﻿//----------------------------------------------------------------
// Copyright (c) 2011-2013 
// All rights reserved.
// Author: Ben DaoWei 
// Contact: bendaowei@gmail.com
// Web Link: http://xnaeditor.codeplex.com
// ----------------------------------------------------------------

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using UnityEngine.Components;
using System.Diagnostics;
using UnityEngine.Primitive;
using UnityEngine.Terrain;
using UnityEngine.Graphics.Shader;
using UnityEngine.Graphics.Light;
using UnityEngine.Gameplay;

namespace UnityEngine.Graphics
{
    public class SceneManager : IDisposable
    {
        public const int MaxPaletteInstanceCount = 1024 * 16;
        public const int MaxSpriteInstanceCount = 256;
        public const int DefaultScreenWidth = 1024;
        public const int DefaultScreenHeight = 768;
        public const int MaxMovingObjectsCount = 2048;
        public const int MaxSeletableObjectCount = 2048;
        public const int MaxAreaLightCount = 128;

        private UnityEngineGame _Game;

        public UnityEngineGame Game
        {
            get
            {
                return _Game;
            }
        }

        public static void SingletonDispose()
        {
            if (_Instance != null)
                _Instance.Dispose();
            _Instance = null;
        }

        ~SceneManager()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_OctTree != null)
                    _OctTree.Dispose();

                if (this._ShadowMap != null)
                    _ShadowMap.Dispose();

                if (this._Portrait != null)
                    _Portrait.Dispose();

                if (this._PaletteRendererManager != null)
                    _PaletteRendererManager.Dispose();
            }
        }


        private PaletteRendererManager _PaletteRendererManager;

        // update list, 2D & 3D
        private int _ToUpdatePaletteInstancesArrayCount;
        private PaletteInstance[] _ToUpdatePaletteInstancesArray;

        // draw list, 2D
        private int _ToDrawSpriteInstanceArrayCount;
        private TwoDInstance[] _ToDrawSpriteInstanceArray;

        // draw list, 3D
        private int _ToDrawPaletteInstancesArrayCount;
        private PaletteInstance[] _ToDrawPaletteInstancesArray;
        private float[] _ToDrawPaletteInstancesDistance;

        // moving objects
        private List<PaletteInstance> _MovingObjectsList;

        private int _AreaLightCount;
        private AreaLightObject[] _AreaLightArray;

        // Scene Manager Algorithm Core, 3D
        private OctTree _OctTree;

        // Camera
        private Camera _Camera;
        private Matrix _View;
        private Matrix _Projection;

        private ShadowMap _ShadowMap;
        private Portrait _Portrait;

        public Portrait Portrait
        {
            get
            {
                return _Portrait;
            }
        }

        public PaletteRendererManager PaletteRendererManager
        {
            get { return _PaletteRendererManager; }
        }

        // Interface
        private static SceneManager _Instance = null;
        public static SceneManager Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new SceneManager();
                return _Instance;
            }
        }

        private SceneManager()
        {
            Reset();
        }

        public void Reset()
        {
            _OctTree = new OctTree(new BoundingBox(new Vector3(-128 * 128, -128 * 128, -128 * 128), new Vector3(128 * 256, 128 * 256, 128 * 256)), 0);

            // draw list, 3D
            _ToDrawPaletteInstancesArrayCount = 0;
            _ToDrawPaletteInstancesArray = new PaletteInstance[MaxPaletteInstanceCount];

            // draw list, 2D
            _ToDrawSpriteInstanceArrayCount = 0;
            _ToDrawSpriteInstanceArray = new TwoDInstance[MaxSpriteInstanceCount];

            // update list, 2D & 3D
            _ToUpdatePaletteInstancesArrayCount = 0;
            _ToUpdatePaletteInstancesArray = new PaletteInstance[MaxPaletteInstanceCount];
            _ToDrawPaletteInstancesDistance = new float[MaxPaletteInstanceCount];

            // moving objects
            _MovingObjectsList = new List<PaletteInstance>();

            _AreaLightCount = 0;
            _AreaLightArray = new AreaLightObject[MaxAreaLightCount];

            _ShadowMap = new ShadowMap();
            _ShadowMap.Startup(null);

            _Portrait = new Portrait();
            _Portrait.Startup(null);
        }

        public void Initialize(UnityEngineGame game)
        {
            _Game = game;
            _PaletteRendererManager = new PaletteRendererManager(game);
            this.Reset();
        }

        #region AreaLights
        public void AddAreaLight(AreaLightObject light)
        {
            _AreaLightArray[_AreaLightCount] = light;
            _AreaLightCount++;
        }

        public void CalculateLights(WarCraftEnvironment environment, VertexBuffer buffer)
        {
            VertexPositionNormalTextureFogBones[] points = new VertexPositionNormalTextureFogBones[buffer.VertexCount];
            buffer.GetData<VertexPositionNormalTextureFogBones>(points);
            for (int pointIndex = 0; pointIndex < points.Length; pointIndex++)
            {
                float current = environment.GetLightValue(points[pointIndex].Position);
                points[pointIndex].Fog = current;
            }
            buffer.SetData<VertexPositionNormalTextureFogBones>(points);
        }

        public void CalculateLights(GameTime time)
        {
            if (_Game.GameScene.GetComponentInChildren<Landscape>() == null)
                return;
            // Calculate lights and store in one array
            _Game.GameScene.GetComponentInChildren<Landscape>().WarCraftEnvironment.CalculateLight(this._AreaLightArray, this._AreaLightCount, time);
            // DrawEverything
            for (int i = 0; i < _ToDrawPaletteInstancesArrayCount; i++)
            {
                PaletteInstance instance = _ToDrawPaletteInstancesArray[i];
                // Landscape Unit
                if (instance is LandscapeCellInstance)
                {
                    LandscapeCellRenderer renderer = instance.PaletteReference.PaletteRenderer as LandscapeCellRenderer;
                    CalculateLights(_Game.GameScene.GetComponentInChildren<Landscape>().WarCraftEnvironment, renderer.VertexBuffer);
                    continue;
                }

                if (instance is CliffInstance)
                {
                    ModelRenderer renderer = instance.PaletteReference.PaletteRenderer as ModelRenderer;
                    foreach (VertexBuffer buffer in renderer.VertexBuffers)
                    {
                        CalculateLights(_Game.GameScene.GetComponentInChildren<Landscape>().WarCraftEnvironment, buffer);
                    }
                    continue;
                }
            }

            // Create shadow map
            _Portrait.UpdatePortrait();
            _ShadowMap.UpdateShadowMap();
        }
        #endregion // Area Lights

        public void RemoveInstance(PaletteInstance Instance)
        {
            // Currently, it does not support dynamically detect moving objects, so save them in an array
            if (Instance.IsMovable)
            {
                _MovingObjectsList.Remove(Instance);
            }
            else
            {
                throw new Exception("Not supported now.");
                if (Instance is SpriteInstance)
                {
                    throw new NotSupportedException("This operation is not supported.");
                }
                else
                {
                    _OctTree.InsertPaletteInstance(Instance);
                }
            }
        }

        public void AddInstance(PaletteInstance Instance)
        {
            // Currently, it does not support dynamically detect moving objects, so save them in an array
            if (Instance.IsMovable)
            {
                _MovingObjectsList.Add(Instance);
            }
            else
            {
                if (Instance is SpriteInstance)
                {
                    throw new NotSupportedException("This operation is not supported.");
                }
                else
                {
                    _OctTree.InsertPaletteInstance(Instance);
                }
            }
        }

        public void Update(Camera Camera)
        {
            // Set Camera
            _Camera = Camera;
            _View = Camera.View;
            _Projection = Camera.Projection;

            // update static objects
            _OctTree.Update(Camera.BoundingFrustum);

            // update moving objects;
            for (int i = 0; i < _MovingObjectsList.Count; i++)
            {
                _MovingObjectsList[i].Update(Camera.BoundingFrustum);
                if (_MovingObjectsList[i].IsInBoundingFrustum)
                    AddIntoRenderQueue(_MovingObjectsList[i]);
            }
        }

        #region update queue
        public void Update(GameTime gameTime)
        {
            for (int i = 0; i < _ToUpdatePaletteInstancesArrayCount; i++)
                _ToUpdatePaletteInstancesArray[i].Update(gameTime);
        }

        public void AddUpdateInstance(PaletteInstance Instance)
        {
            _ToUpdatePaletteInstancesArray[_ToUpdatePaletteInstancesArrayCount] = Instance;
            _ToUpdatePaletteInstancesArrayCount++;
        }
        #endregion

        #region render queue
        public void AddIntoRenderQueue(PaletteInstance Instance)
        {
            if (Instance is TwoDInstance)
            {
                _ToDrawSpriteInstanceArray[_ToDrawSpriteInstanceArrayCount++] = Instance as TwoDInstance;
            }
            else
            {
                _ToDrawPaletteInstancesArray[_ToDrawPaletteInstancesArrayCount++] = Instance;
            }
        }

        private void SortPaletteInstances(int f, int r)
        {
            int mid = (f + r) / 2;

            // record mid object
            float dis = _ToDrawPaletteInstancesDistance[mid];
            int rqv = _ToDrawPaletteInstancesArray[mid].RenderQueueValue;
            PaletteInstance ins = _ToDrawPaletteInstancesArray[mid];

            // move front to mid
            _ToDrawPaletteInstancesDistance[mid] = _ToDrawPaletteInstancesDistance[f];
            _ToDrawPaletteInstancesArray[mid] = _ToDrawPaletteInstancesArray[f];

            // start quick sort
            int ff = f;
            int rr = r;
            while (ff < rr)
            {
                while (ff < rr &&
                    (rqv < _ToDrawPaletteInstancesArray[rr].RenderQueueValue ||
                     rqv == _ToDrawPaletteInstancesArray[rr].RenderQueueValue && dis <= _ToDrawPaletteInstancesDistance[rr])) rr--;

                _ToDrawPaletteInstancesDistance[ff] = _ToDrawPaletteInstancesDistance[rr];
                _ToDrawPaletteInstancesArray[ff] = _ToDrawPaletteInstancesArray[rr];

                while (ff < rr &&
                    (_ToDrawPaletteInstancesArray[ff].RenderQueueValue < rqv ||
                     _ToDrawPaletteInstancesArray[ff].RenderQueueValue == rqv && _ToDrawPaletteInstancesDistance[ff] <= dis))
                    ff++;

                _ToDrawPaletteInstancesDistance[rr] = _ToDrawPaletteInstancesDistance[ff];
                _ToDrawPaletteInstancesArray[rr] = _ToDrawPaletteInstancesArray[ff];
            }

            // move temp to ff
            _ToDrawPaletteInstancesDistance[ff] = dis;
            _ToDrawPaletteInstancesArray[ff] = ins;

            if (f < ff - 1) SortPaletteInstances(f, ff - 1);
            if (ff + 1 < r) SortPaletteInstances(ff + 1, r);
        }

        private void SortTwoDInstances(int f, int r)
        {
            int mid = (f + r) / 2;

            // record mid object
            int rqv = _ToDrawSpriteInstanceArray[mid].RenderQueueValue;
            TwoDInstance ins = _ToDrawSpriteInstanceArray[mid];

            // move front to mid
            _ToDrawSpriteInstanceArray[mid] = _ToDrawSpriteInstanceArray[f];

            // start quick sort
            int ff = f;
            int rr = r;
            while (ff < rr)
            {
                while (ff < rr && rqv <= _ToDrawSpriteInstanceArray[rr].RenderQueueValue) rr--;
                _ToDrawSpriteInstanceArray[ff] = _ToDrawSpriteInstanceArray[rr];
                while (ff < rr && _ToDrawSpriteInstanceArray[ff].RenderQueueValue < rqv) ff++;
                _ToDrawSpriteInstanceArray[rr] = _ToDrawSpriteInstanceArray[ff];
            }

            // move temp to ff
            _ToDrawSpriteInstanceArray[ff] = ins;

            if (f < ff - 1) SortTwoDInstances(f, ff - 1);
            if (ff + 1 < r) SortTwoDInstances(ff + 1, r);
        }

        public void Sort()
        {
            // Sort 3D part
            // calculate distance of each object to camera
            for (int i = 0; i < _ToDrawPaletteInstancesArrayCount; i++)
                _ToDrawPaletteInstancesDistance[i] = Vector3.Distance(this._Camera.Position, _ToDrawPaletteInstancesArray[i].Position);

            // sort them from near to far away
            SortPaletteInstances(0, _ToDrawPaletteInstancesArrayCount - 1);

            // Sort 2D part
            for (int i = 0; i < _ToDrawSpriteInstanceArrayCount; i++)
                _ToDrawSpriteInstanceArray[i].CalculateRenderQueueValue(i);
            SortTwoDInstances(0, _ToDrawSpriteInstanceArrayCount - 1);
        }

        public void RenderForShadow()
        {
            // 3D parts
            for (int i = 0; i < _ToDrawPaletteInstancesArrayCount; i++)
            {
                if (_ToDrawPaletteInstancesArray[i].IsCreateShadow == false)
                    continue;

                if (_ToDrawPaletteInstancesArray[i].IsTransparent == false)
                    _ToDrawPaletteInstancesArray[i].Draw(_View, _Projection);
            }

            for (int i = _ToDrawPaletteInstancesArrayCount - 1; i >= 0; i--)
            {
                if (_ToDrawPaletteInstancesArray[i].IsCreateShadow == false)
                    continue;

                if (_ToDrawPaletteInstancesArray[i].IsTransparent == true)
                    _ToDrawPaletteInstancesArray[i].Draw(_View, _Projection);
            }
        }

        public void Render3D()
        {
            // 3D parts
            for (int i = 0; i < _ToDrawPaletteInstancesArrayCount; i++)
            {
                if (_ToDrawPaletteInstancesArray[i].IsTransparent == false)
                    _ToDrawPaletteInstancesArray[i].Draw(_View, _Projection);
            }

            for (int i = _ToDrawPaletteInstancesArrayCount - 1; i >= 0; i--)
            {
                if (_ToDrawPaletteInstancesArray[i].IsTransparent == true)
                    _ToDrawPaletteInstancesArray[i].Draw(_View, _Projection);
            }
        }

        public void Render2D()
        {
            // 2D parts
            if (_ToDrawSpriteInstanceArrayCount > 0)
            {
                for (int i = 0; i < _ToDrawSpriteInstanceArrayCount; i++)
                {
                    if (i == 0 ||
                        _ToDrawSpriteInstanceArray[i - 1].PaletteReference.PaletteRenderer != _ToDrawSpriteInstanceArray[i].PaletteReference.PaletteRenderer)
                        (_ToDrawSpriteInstanceArray[i].PaletteReference.PaletteRenderer as SpriteRenderer).Begin();

                    _ToDrawSpriteInstanceArray[i].Draw(_Game.GameSettings.ScreenWidth, _Game.GameSettings.ScreenHeight);

                    if (i == _ToDrawSpriteInstanceArrayCount - 1 ||
                        _ToDrawSpriteInstanceArray[i].PaletteReference.PaletteRenderer != _ToDrawSpriteInstanceArray[i + 1].PaletteReference.PaletteRenderer)
                        (_ToDrawSpriteInstanceArray[i].PaletteReference.PaletteRenderer as SpriteRenderer).End();
                }
            }
        }

        public void ClearRenderQueue()
        {
            _ToDrawPaletteInstancesArrayCount = 0;
            _ToDrawSpriteInstanceArrayCount = 0;
            _AreaLightCount = 0;

            EffectManager.Instance.GraphicsDevice.Textures[0] = null;
            EffectManager.Instance.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
        }
        #endregion // render queue
    }
}
